package log

import (
	"fmt"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	aactor "github.com/asynkron/protoactor-go/actor"
	"github.com/spf13/viper"

	kadefine "kiwi/actor/define"
	kutil "kiwi/util"
)

type ILogger interface {
	Destroy()
	SetLevel(level Level)
	Debug(args ...any)
	Debugf(format string, args ...any)
	Info(args ...any)
	Infof(format string, args ...any)
	Warn(args ...any)
	Warnf(format string, args ...any)
	Error(args ...any)
	Errorf(format string, args ...any)
}

type LoggerOption struct {
	Name            string
	Level           Level
	TimestampFormat string
	Caller          bool
	CallerSkip      int
	Console         bool
	ConsoleColorful bool
}

func (o *LoggerOption) Load(cfg *viper.Viper) {
	if cfg == nil {
		return
	}

	o.Name = cfg.GetString("name")

	sLevel := strings.ToLower(cfg.GetString("level"))
	switch sLevel {
	case "debug":
		o.Level = LevelDebug
	case "info":
		o.Level = LevelInfo
	case "warn":
		o.Level = LevelWarn
	case "error":
		o.Level = LevelError
	default:
		o.Level = LevelDebug
	}

	o.TimestampFormat = cfg.GetString("timestampformat")

	o.Caller = cfg.GetBool("caller")

	o.CallerSkip = cfg.GetInt("callerskip")

	o.Console = cfg.GetBool("console")

	o.ConsoleColorful = cfg.GetBool("consolecolorful")
}

type logger struct {
	LoggerOption

	level atomic.Int32

	consoleWriter *consoleWriter

	writers []IWriter

	startedOnce sync.Once
	startedWg   sync.WaitGroup

	stoppedOnce sync.Once

	pid *kadefine.PID

	count sync.WaitGroup
}

func CreateLogger(option *LoggerOption, writers ...IWriter) (ILogger, error) {
	if option == nil {
		option = &LoggerOption{}
	}

	l := &logger{
		LoggerOption: *option,
	}

	var err error

	if l.Name == "" {
		l.Name, err = kutil.GetProgramFileBaseName()
		if err != nil {
			return nil, fmt.Errorf("get logger name fail, %w", err)
		}
	}
	if _, ok := loggers.Load(l.Name); ok {
		return nil, fmt.Errorf("logger[%s] already exist", l.Name)
	}

	if !l.Level.Valid() {
		l.Level = LevelDebug
	}
	l.level.Store(int32(l.Level))

	if l.TimestampFormat == "" {
		l.TimestampFormat = "2006/01/02T15:04:05.000"
	}

	if l.Console {
		l.consoleWriter = createConsoleWriter(l.ConsoleColorful)
	}

	for _, w := range writers {
		if w != nil {
			l.writers = append(l.writers, w)
		}
	}

	if len(l.writers) == 0 && l.consoleWriter == nil {
		l.consoleWriter = createConsoleWriter(l.ConsoleColorful)
	}

	l.startedWg.Add(1)

	props := aactor.PropsFromProducer(func() aactor.Actor { return l } /*, aactor.WithMailbox(aactor.Unbounded())*/)
	name := "logger-" + l.Name
	_, err = rootContext().SpawnNamed(props, name)
	if err != nil {
		return nil, fmt.Errorf("spawn logger fail, name=%s, %v", name, err)
	}

	loggers.Store(l.Name, l)

	l.startedWg.Wait()

	return l, nil
}

func MustCreateLogger(option *LoggerOption, writers ...IWriter) ILogger {
	l, err := CreateLogger(option, writers...)
	if err != nil {
		panic(err)
	}
	return l
}

func (l *logger) Destroy() {
	rootContext().Poison(l.pid)
	l.count.Wait()
}

func (l *logger) SetLevel(level Level) {
	if !level.Valid() {
		return
	}
	l.level.Store(int32(level))
}

func (l *logger) canOutput(level Level) bool {
	if !level.Valid() {
		return false
	}
	if level < Level(l.level.Load()) {
		return false
	}
	return true
}

func (l *logger) log(level Level, content string) {
	r := createRecord()

	r.timestamp = time.Now().Format(l.TimestampFormat)
	r.level = level
	r.prefix = l.Name
	r.content = content

	if l.Caller {
		var ok bool
		_, r.path, r.line, ok = runtime.Caller(l.CallerSkip)
		if !ok {
			r.path = "???"
			r.line = 0
		}
	}

	l.count.Add(1)

	rootContext().Send(l.pid, r)
}

func (l *logger) Receive(ctx kadefine.Context) {
	switch msg := ctx.Message().(type) {
	case *record:
		defer l.count.Done()

		msg.buildBytes()

		if l.consoleWriter != nil {
			l.consoleWriter.Write(msg)
		}

		for i, w := range l.writers {
			if i == 0 {
				w.Write(msg)
			} else {
				w.Write(msg.Clone())
			}
		}

	case *kadefine.Started:
		l.startedOnce.Do(func() {
			l.pid = ctx.Self().Clone()

			l.startedWg.Done()
		})

	case *kadefine.Stopped:
		l.stoppedOnce.Do(func() {
			clear(l.writers)
		})
	}
}

func (l *logger) Debug(args ...any) {
	if !l.canOutput(LevelDebug) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelDebug, m)
}

func (l *logger) Debugf(format string, args ...any) {
	if !l.canOutput(LevelDebug) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelDebug, m)
}

func (l *logger) Info(args ...any) {
	if !l.canOutput(LevelInfo) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelInfo, m)
}

func (l *logger) Infof(format string, args ...any) {
	if !l.canOutput(LevelInfo) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelInfo, m)
}

func (l *logger) Warn(args ...any) {
	if !l.canOutput(LevelWarn) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelWarn, m)
}

func (l *logger) Warnf(format string, args ...any) {
	if !l.canOutput(LevelWarn) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelWarn, m)
}

func (l *logger) Error(args ...any) {
	if !l.canOutput(LevelError) {
		return
	}
	m := fmt.Sprint(args...)
	l.log(LevelError, m)
}

func (l *logger) Errorf(format string, args ...any) {
	if !l.canOutput(LevelError) {
		return
	}
	m := fmt.Sprintf(format, args...)
	l.log(LevelError, m)
}
